from math import sqrt
# -----------------------------------------------------------------------------------------
# 定义用于处理Ply模型顶点的类,可作为向量使用
# 顶点有三个主参数x,y,z与一个副参数otherInfo
#
# 
# -----------------------------------------------------------------------------------------
class PlyVertex:
    '''
    定义用于处理Ply模型顶点的类,可作为向量使用
    '''
    def __init__(self, x:float, y:float, z:float, otherInfo=''):
        self.__x = float(x)
        self.__y = float(y)
        self.__z = float(z)
        self.__otherInfo = otherInfo
        # 访问时才会初始化的成员变量
        self.__str = None
        self.__info = None
        self.__hash = None
        self.__norm = None
        self.__unitVector = None
        
    # 设置部分属性为只读
    @property
    def unitVector(self):
        if self.__unitVector == None:
            self.__unitVector = self.div(self.norm)
        return self.__unitVector
    @property
    def x(self):
        return self.__x
    @property
    def y(self):
        return self.__y
    @property
    def z(self):
        return self.__z
    @property
    def otherInfo(self):
        return self.__otherInfo
    @property
    def hash(self):
        if self.__hash == None:
            self.__hash = hash(self.info)
        return self.__hash
    @property
    def info(self):
        if self.__info ==  None:
            self.__info = f'{type(self)}:[x:{self.__x}, y:{self.__y} z:{self.__z}, otherInfo:{self.__otherInfo}]'
        return self.__info
    @property
    def norm(self):
        if self.__norm == None:
            self.__norm = sqrt(self.__x**2 + self.__y**2 + self.__z**2)
        return self.__norm

    # 重载部分函数
    def __str__(self) -> str:
        if self.__str == None:
            self.__str = f'{self.__x} {self.__y} {self.__z} {self.__otherInfo}'
        return self.__str
    def __hash__(self) -> int:
        return self.hash
    def __eq__(self, o) -> bool:
        if type(self) == type(o):
            return self.__x ==  o.__x and self.__y==o.__y and self.__z ==o.__z
        return False
        
    def __repr__(self) -> str:
        return self.info
    def __lt__(self, v) -> bool:
        ''' < '''
        if self.__x < v.__x:
            return True
        elif self.__y < v.__y:
            return True
        elif self.__z < v.__z:
            return True
        return False
    def __gt__(self, v) ->bool:
        ''' > '''
        if self < v or self == v:
            return False
        return True
    def __add__(self, v):
        return PlyVertex(self.__x+v.__x, self.y+v.__y, self.__z+v.__z)
    def __sub__(self, v):
        return PlyVertex(self.__x-v.__x, self.y-v.__y, self.__z-v.__z)
    # 功能函数
    def inner(self, v):
        '''计算内积(点积)'''
        return self.x*v.x + self.y*v.y + self.z*v.z

    def product(self, v):
        '''计算外积(叉积)'''
        x = self.y*v.z - self.z*v.y
        y = self.z*v.x - self.x*v.z
        z = self.x*v.y - self.y*v.x
        return PlyVertex(x, y, z)

    def cosA(self, v):
        '''计算向量之间夹角的余弦值'''
        return self.inner(v)/(self.norm*v.norm)

    def div(self, i:float):
        return PlyVertex(self.__x/i, self.__y/i, self.__z/i)


# -----------------------------------------------------------------------------------------
# 定义用于处理Ply模型边的类
# 边有两个主参数startVertex, endVertex
#
# 
# -----------------------------------------------------------------------------------------
class PlyEdge:
    '''
    边
    '''
    def __init__(self,startVer:PlyVertex, endVer:PlyVertex):
        self.__startVer = startVer
        self.__endVer = endVer
        # 访问时才会初始化的成员变量
        self.__vector = None
        self.__norm = None
        self.__hash = None
        self.__center = None

    # 设置部分属性为只读
    @property
    def startVer(self):
        return self.__startVer
    @property
    def endVer(self):
        return self.__endVer
    @property
    def center(self) -> PlyVertex:
        if self.__center == None:
            self.__center = self.__vector.div(2) + self.startVer
        return self.__center
    @property
    def vector(self) -> PlyVertex:
        if self.__vector == None:
            self.__vector = self.__endVer - self.__startVer
        return self.__vector
    @property
    def norm(self) -> float:
        if self.__norm == None:
            self.__norm = self.vector.norm
    @property
    def hash(self):
        if self.__hash == None:
            self.__hash = hash(f'{type(self)}@[{self.__startVer} -> {self.__endVer}]')
        return self.__hash
    # 重载部分函数
    def __lt__(self, e):
        return self.center < e.center
    def __hash__(self) -> int:
        return self.hash
    def __eq__(self, e):
        if hash(self) == hash(e):
            return True
        return False
    # 功能函数
    def isPubEdge(self, e):
        '''
        判断两个边是否为公共边
        - 返回值
            - 0 不是公共边
            - <0 是公共边(方向相反)
            - >0 是公共边(方向相同)
        '''
        if type(self) == type(e):
            if self.__startVer == e.__startVer and self.__endVer == e.__endVer:
                return 1
            elif self.__startVer == e.__endVer and self.__endVer == e.__startVer:
                return -1
        return 0

# -----------------------------------------------------------------------------------------
# 定义用于处理Ply模型面的类
# 边有一个主参数edgeList
#
# 
# -----------------------------------------------------------------------------------------
class PlyFace:
    '''
    面
    '''
    def __init__(self, verList:list):
        self.__verList = verList.copy()
        # 访问时才会初始化的成员变量
        self.__edgeList = None
        self.__center, self.__maxV, self.__minV = None, None, None
        self.__normal, self.__T = None, None
        self.__verDict = None

    # 设置部分属性为只读
    @property
    def verDict(self):
        if self.__verDict == None:
            verList, index, verDict = self.__verList, 0, {}
            for v in verList:
                verDict[v] = index
                index += 1
            self.__verDict = verDict
        return self.__verDict.copy()
    @property
    def edgeList(self):
        if self.__edgeList == None:
            vl = self.__verList
            self.__edgeList = [PlyEdge(vl[i-1], vl[i]) for i in range(len(self.__verList))]
        return self.__edgeList.copy()
    @property
    def T(self):
        if self.__T == None:
            vn = self.normal
            x, y, z = vn.x!= 0, vn.y!=0, vn.z!=0
            if x+y+z > 1:
                self.__T = 'o'
            elif x:
                self.__T = 'x'
            elif y:
                self.__T = 'y'
            elif z:
                self.__T = 'z'
            if vn.x + vn.y + vn.z > 0:
                self.__T = self.T.upper()
        return self.__T
    @property
    def normal(self):
        if self.__normal == None:
            el = self.edgeList
            v1 = el[-1].vector
            for e in el:
                v2 = e.vector
                ta, tb = v1.cosA(v2), 0.26
                if v1 != v2 and ta < tb and ta > -tb:
                    self.__normal = v1.product(v2).unitVector
                    break
                v1 = v2
        return self.__normal
    @property
    def maxV(self):
        if self.__maxV == None:
            self.center
        return self.__maxV
    @property
    def minV(self):
        if self.__minV == None:
            self.center
        return self.__minV
    @property
    def center(self):
        if self.__center == None:
            vl = self.__verList
            minV, maxV = vl[0], vl[0]
            for v in vl:
                if v < minV:
                    minV = v
                elif v >maxV:
                    maxV = v
            self.__minV, self.__maxV, self.__center = minV, maxV, (maxV+minV).div(2)
        return self.__center
    @property
    def verList(self):
        return self.__verList.copy()
    
    # 重载部分函数
    def __getitem__(self, index):
        return self.__verList[index]
    def __len__(self):
        return len(self.__verList)
    def __str__(self) -> str:
        s = f'[ n = {len(self)} T = {self.T}'
        for v in self:
            s += '\n' + str(v)
        s += ']'
        return s
    def __lt__(self, f):
        t1 = self.T
        t2 = f.T
        if t1 < t2:
            return True
        elif t1 == t2:
            return self.center < f.center
        return False
    # 功能函数
    def indexE(self, e:PlyEdge) -> int:
        if e in self.__edgeList:
            return self.__edgeList.index(e)
        return -1
    def index(self, v:PlyVertex) -> int:
        verDict = self.verDict
        if v in verDict:
            return verDict[v]
        return -1
 


class PlyModel:
    '''
    Ply模型类
    '''
    def __init__(self, faceList:list):       
        self.__faceList = faceList.copy()
        # 访问时才会初始化的成员变量
        self.__verList = None
        self.__verDict = None
        self.__faceDict = None
        self.__info = None
    
    #设置部分属性为只读
    @property
    def verDict(self):
        if self.__verDict == None:
            self.verList
        return self.__verDict.copy()
    @property
    def faceDict(self):
        if self.__faceDict == None:
            faceDict = {}
            for f in self:
                t = f.T
                if t in faceDict:
                    faceDict[t].append(f)
                else:
                    faceDict[t] = [f]
            self.__faceDict = faceDict
        return self.__faceDict.copy()
    @property
    def verList(self):
        if self.__verList == None:
            verList, faceList, verDict = [], self.__faceList, {}
            for f in faceList:
                for v in f:
                    verDict[v] = 1
            verList = list(verDict)
            verList.sort()
            index = 0
            for v in verList:
                verDict[v] = index
                index += 1
            self.__verDict = verDict
            self.__verList = verList
        return self.__verList
    @property
    def faceList(self):
        return self.__faceList.copy()
    @property
    def info(self):
        if self.__info == None:
            faceDict, s = self.faceDict, ''
            for key in faceDict:
                s += f'{key}:{len(faceDict[key])} '
            self.__info = f'verNum = {len(self.verList)}\tfaceNum = {len(self.__faceList)} [{s}]'
        return self.__info
    # 重载部分运算符
    def __getitem__(self, index) -> PlyFace:
        return self.__faceList[index]
    def __len__(self):
        return len(self.__faceList)
    # 功能函数
    def index(self, f:PlyFace):
        faceDict = self.faceDict
        if f in faceDict:
            return faceDict[f]
        return -1
    def faceNum(self):
        return len(self.__faceList)
    def verNum(self):
        return len(self.__verList)